//////////////////////////////////////////////////
// Silex, live web creation
// http://projects.silexlabs.org/?/silex/
//
// Copyright (c) 2012 Silex Labs
// http://www.silexlabs.org/
//
// Silex is available under the GPL license
// http://www.silexlabs.org/silex/silex-licensing/
//////////////////////////////////////////////////

import * as CloudExplorer from 'cloud-explorer';
import * as express from 'express';
import * as fs from 'fs';
import { JSDOM } from 'jsdom';
import * as nodeModules from 'node_modules-path';
import * as Path from 'path';
import { URL } from 'url';
import { Constants } from '../../Constants';
import BackwardCompat from '../utils/BackwardCompat';
import DomTools from '../utils/DomTools';

export default function({ port, rootUrl }, unifile) {

  const backwardCompat = new BackwardCompat(rootUrl);

  function isInTemplateFolder(path) {
    if (path.startsWith(Path.join(nodeModules('silex-templates'), 'silex-templates')) ||
      path.startsWith(Path.join(nodeModules('silex-blank-templates'), 'silex-blank-templates'))) {
        return true;
    }
    return false;
  }
  /**
   * list all the templates of the given folder
   */
  function getTemplatesList(req, res, next) {
    const templateFolder = Path.join(nodeModules(req.params.folder), req.params.folder);
    if (!isInTemplateFolder(templateFolder)) {
        console.error('Error while trying to get the json representation of the folder ', templateFolder);
        res.send({success: false, error: 'Error while trying to get the json representation of the folder ' + req.params.folder + ' - folder does not exist'});
        return;
    }
    fs.readdir(templateFolder, (err, result) => {
      if (err) {
        console.error('Error while trying to get the json representation of the folder ', templateFolder, err);
        res.send({success: false, error: 'Error while trying to get the json representation of the folder ' + req.params.folder + ' - ' + err});
      } else {
        const templateList = result.filter((entry) => {
          return fs.statSync(Path.join(templateFolder, entry)).isDirectory();
        });

        res.send(templateList);
      }
    });
  }
  /**
   * load a website from the cloud storage of the user
   */
  function readWebsite(req, res, next) {
    const connector = req.params[0];
    const path = req.params[1];
    const url = new URL(`${ rootUrl }/ce/${ connector }/get/${ Path.dirname(path) }/`);
    unifile.readFile(req.session.unifile, connector, path)
      .then((buffer) => {
        return sendWebsiteData(res, buffer, url);
      })
      .catch((err) => {
        console.error('unifile error catched:', err);
        CloudExplorer.handleError(res, err);
      });
  }
  /**
   * load a website from a template folder on local disk
   */
  function readTemplate(req, res, next) {
    const path = req.params[0];
    const localPath = Path.resolve(nodeModules('silex-templates'), path);
    const url = new URL(`${ rootUrl }/libs/templates/${ Path.dirname(path) }/`);
    if (isInTemplateFolder(localPath)) {
      fs.readFile(localPath, (err, buffer) => {
        if (err) {
          CloudExplorer.handleError(res, err);
        } else {
          sendWebsiteData(res, buffer, url);
        }
      });
    } else {
      CloudExplorer.handleError(res, {message: 'Not authorized.', code: 'EACCES'});
    }
  }
  function sendWebsiteData(res, buffer, url): Promise<void> {
    // remove user head tag to avoid bad markup messing with the website
    const { html, userHead } = DomTools.extractUserHeadTag(buffer.toString('utf-8'));
    // from now on use a parsed DOM
    const dom = new JSDOM(html, { url: url.href });
    if (dom.window.document.body.classList.contains(Constants.WEBSITE_CONTEXT_PUBLISHED_CLASS_NAME)) {
      console.error('Could not open this website for edition as it is a published Silex website');
      res.status(400).send({
        message: 'Could not open this website for edition as it is a published Silex website, <a href="https://github.com/silexlabs/Silex/wiki/FAQ#why-do-i-get-the-error-could-not-open-this-website-for-edition-as-it-is-a-published-silex-website" target="_blank">Read more about this error here</a>.',
      });
    } else {
      return backwardCompat.update(dom.window.document)
      .then((wanrningMsg) => {
        prepareWebsite(dom, url);
        // done, back to a string
        const str = dom.serialize();
        dom.window.close();
        res.send({
          message: wanrningMsg,
          html: str,
          userHead,
        });
      })
      .catch((err) => {
        console.error('Could not send website data: ', err);
        res.status(400).send({
          message: err.message,
        });
      });
    }
  }
  /**
   * save a website to the cloud storage of the user
   */
  function writeWebsite(req, res, next) {
    const connector = req.params[0];
    const path = req.params[1];
    const { userHead, html } = JSON.parse(req.body);
    const url = new URL(`${ rootUrl }/ce/${ connector }/get/${ Path.dirname(path) }/`);
    const dom = new JSDOM(html, { url: url.href });
    unprepareWebsite(dom, url);
    const str = dom.serialize();
    const fullHtml = DomTools.insertUserHeadTag(str, userHead);
    dom.window.close();

    unifile.writeFile(req.session.unifile, connector, req.params[1], fullHtml)
      .then((result) => {
        res.send(result);
      })
      .catch((err) => {
        console.error('unifile error catched:', err);
        CloudExplorer.handleError(res, err);
      });
  }
  /**
   * prepare website for edit mode
   * make all URLs absolute (so that images are still found when I "save as" my website to another folder)
   */
  function prepareWebsite(dom, baseUrl) {
    // URLs
    DomTools.transformPaths(dom, (path, el) => {
      const url = new URL(path, baseUrl);
      return url.href;
    });
    // update context classes
    dom.window.document.body.classList.remove(Constants.WEBSITE_CONTEXT_RUNTIME_CLASS_NAME);
    dom.window.document.body.classList.add(Constants.WEBSITE_CONTEXT_EDITOR_CLASS_NAME);
    deactivateScripts(dom);
    // add /css/editable.css
    const tag = dom.window.document.createElement('link');
    tag.rel = 'stylesheet';
    tag.href = rootUrl + '/css/editable.css';
    tag.classList.add(Constants.SILEX_TEMP_TAGS_CSS_CLASS);
    dom.window.document.head.appendChild(tag);
  }
  /**
   * prepare website for being saved
   * * make all URLs relative to current path
   * * remove useless markup and css classes
   */
  function unprepareWebsite(dom, baseUrl) {
    // markup
    dom.window.document.body.classList.add(Constants.WEBSITE_CONTEXT_RUNTIME_CLASS_NAME);
    dom.window.document.body.classList.remove(Constants.WEBSITE_CONTEXT_EDITOR_CLASS_NAME);
    reactivateScripts(dom);
    restoreIFrames(dom);
    cleanupNoscripts(dom);
    // URLs
    DomTools.transformPaths(dom, (path, el) => {
      const url = new URL(path, baseUrl);
      if (url.href.startsWith(rootUrl)) {
        // make it relative
        return Path.relative(baseUrl.pathname, url.pathname);
      }
      return path;
    });
    // remove temp tags
    Array.from(dom.window.document.querySelectorAll(`
      .${Constants.SILEX_TEMP_TAGS_CSS_CLASS},
      #${Constants.SILEX_CURRENT_PAGE_ID},
      .${ Constants.RISZE_HANDLE_CSS_CLASS }`))
    .forEach((el: HTMLElement) => {
      el.remove();
    });
    // remove useless css classes
    Constants.SILEX_TEMP_CLASS_NAMES.forEach((className) => {
      Array.from(dom.window.document.getElementsByClassName(className))
      .forEach((el: HTMLElement) => el.classList.remove(className));
    });
    // cleanup inline styles
    dom.window.document.body.style.minWidth = ''; // not needed?
    dom.window.document.body.style.minHeight = ''; // not needed?
    dom.window.document.body.style.overflow = ''; // set by stage
  }

  function deactivateScripts(dom) {
    Array.from(dom.window.document.getElementsByTagName('script'))
    .forEach((el: HTMLElement) => {
      // do not execute scripts, unless they are silex's static scripts
      // and leave it alone if it has a type different from 'text/javascript'
      if (!el.hasAttribute('data-silex-static') && (!el.hasAttribute('type') || el.getAttribute('type') === 'text/javascript')) {
        el.setAttribute('type', 'text/notjavascript');
      }
    });
  }

  function restoreIFrames(dom) {
    Array.from(dom.window.document.querySelectorAll('[data-silex-iframe-src]'))
    .forEach((el: HTMLElement) => {
      el.setAttribute('src', el.getAttribute('data-silex-iframe-src'));
      el.removeAttribute('data-silex-iframe-src');
    });
  }

  function cleanupNoscripts(dom) {
    Array.from(dom.window.document.querySelectorAll('noscript'))
    .forEach((el: HTMLElement) => {
      el.innerHTML = decodeHTMLEntities(el.innerHTML);
    });
  }
  function decodeHTMLEntities(text) {
    const entities = [['amp', '&'], ['apos', '\''], ['#x27', '\''], ['#x2F', '/'], ['#39', '\''], ['#47', '/'], ['lt', '<'], ['gt', '>'], ['nbsp', ' '], ['quot', '"']];
    entities.forEach((entity) => text = text.replace(new RegExp('&' + entity[0] + ';', 'g'), entity[1]));
    return text;
  }
  function reactivateScripts(dom) {
    Array.from(dom.window.document.querySelectorAll('script[type="text/notjavascript"]'))
    .forEach((el: HTMLElement) => el.setAttribute('type', 'text/javascript'));
  }

  const router = express.Router();

  // website specials
  router.get(/\/website\/ce\/(.*)\/get\/(.*)/, readWebsite);
  router.get(/\/website\/libs\/templates\/(.*)/, readTemplate);
  router.put(/\/website\/ce\/(.*)\/put\/(.*)/, writeWebsite);

  // **
  // list templates
  router.use('/get/:folder', getTemplatesList);

  return router;
}
